import os
import hashlib
from typing import Optional, List, TypeAlias, Dict, Union, Tuple
from pathlib import Path
from collections import namedtuple

from aiohttp import ClientSession
import asyncio


FileTree: TypeAlias = Dict[str, Union[str, Dict]]


class FileDownload:
    """Инструменты, относящиеся к загрузке файлов с репозитория"""

    def check_tree_element_is_directory(
        self, file_tree: FileTree, element: str
    ) -> bool:
        """Проверяем, является ли элемент дерева директорией"""

        if isinstance(file_tree[element], dict):
            return True
        else:
            return False

    def get_path_to_uploaded_files(self) -> str:
        """Получаем путь к загруженным файлам"""

        path_to_uploaded_files = os.path.abspath(
            os.path.join(os.path.dirname(__name__), "uploaded_files")
        )
        return path_to_uploaded_files

    def create_directories_for_file_tree(self, file_tree: FileTree) -> None:
        """Создаем директории для дерева файлов"""

        path_to_uploaded_files = self.get_path_to_uploaded_files()

        for element in file_tree:
            element_is_directory = self.check_tree_element_is_directory(
                file_tree, element
            )
            if not element_is_directory:
                continue

            directory_path = path_to_uploaded_files + f"/{element}"
            Path(directory_path).mkdir(parents=True, exist_ok=True)
            self.create_directories_for_file_tree(file_tree[element])

    async def download_file(self, file_url: str, file_path: str) -> None:
        """Загружаем файл"""

        async with ClientSession() as session:
            async with session.get(url=file_url) as response:
                file_content = await response.read()
                with open(file_path, "wb") as file:
                    file.write(file_content)

    async def get_list_of_tasks_for_downloading_files(
        self,
        file_tree: FileTree,
        task_list_of_downloading_files: List[asyncio.Task],
        directory: Optional[str] = None,
    ) -> None:
        """Получаем список задач для загрузки файлов"""

        for element in file_tree:
            element_is_directory = self.check_tree_element_is_directory(
                file_tree, element
            )
            if element_is_directory:
                await self.get_list_of_tasks_for_downloading_files(
                    file_tree[element], task_list_of_downloading_files, element
                )
            else:
                path_to_uploaded_files = self.get_path_to_uploaded_files()
                if directory:
                    file_path = f"{path_to_uploaded_files}/{directory}/{element}"
                else:
                    file_path = f"{path_to_uploaded_files}/{element}"
                task = asyncio.create_task(
                    self.download_file(file_tree[element], file_path)
                )
                task_list_of_downloading_files.append(task)

    def get_list_of_downloaded_file_path(
        self, path_list, directory: str, subdirectory: Optional[str] = None
    ) -> None:
        """Получаем список путей файлов указанной директории"""

        list_of_downloaded_files = os.listdir(directory)

        for file_name in list_of_downloaded_files:
            file_path = os.path.abspath(
                os.path.join(os.path.dirname("nitpick"), "uploaded_files")
            )

            if subdirectory:
                file_path = f"{file_path}/{subdirectory}/{file_name}"
            else:
                file_path = f"{file_path}/{file_name}"

            if os.path.isdir(file_path):
                self.get_list_of_downloaded_file_path(path_list, file_path, file_name)
            else:
                path_list.append(file_path)

    def get_sha256_hash_of_file(self, file_path: str) -> str:
        """Получаем sha256 хэш файла"""

        with open(file_path, "rb") as file:
            bytes = file.read()
            hash = hashlib.sha256(bytes).hexdigest()
            return hash

    def get_list_of_sha256_file_hashes(self, list_of_file_paths: List[str]) -> List[Tuple]:
        """Получаем список sha256 хешей переданных файлов"""

        list_of_sha256_file_hashes = []
        FilePathHash = namedtuple("FilePathHash", ["file_path", "hash"])

        for file_path in list_of_file_paths:
            hash = self.get_sha256_hash_of_file(file_path)
            file_path_hash = FilePathHash(file_path, hash)
            list_of_sha256_file_hashes.append(file_path_hash)

        return list_of_sha256_file_hashes

    def get_list_of_sha256_hashes_of_uploaded_files(self) -> List[Tuple]:
        """Получаем список sha256 хешей загруженных файлов"""

        list_of_downloaded_file_paths = []
        path_to_uploaded_files = self.get_path_to_uploaded_files()
        self.get_list_of_downloaded_file_path(
            list_of_downloaded_file_paths, path_to_uploaded_files
        )
        return self.get_list_of_sha256_file_hashes(list_of_downloaded_file_paths)

    def show_sha256_hash_files(self) -> None:
        """Показываем sha256 хэши файлов"""

        list_of_sha256_file_hashes = self.get_list_of_sha256_hashes_of_uploaded_files()

        for count, element in enumerate(list_of_sha256_file_hashes):
            message = f"[*] File number {count}\n{element.file_path}\n{element.hash}\n"
            print(message)
